﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;

namespace BesAsm.Emgaats.Maintenance
{
    internal class FieldMapping
    {
        private Dictionary<string, Field> _mapping = new Dictionary<string, Field>(StringComparer.InvariantCultureIgnoreCase);

        public void AddField(Field field)
        {
            _mapping.Add(field.Name, field);
        }

        public void RemoveField(Field field)
        {
            this.RemoveField(field.Name);
        }

        public void RemoveField(string fieldName)
        {
            if (_mapping.ContainsKey(fieldName))
                _mapping.Remove(fieldName);
        }

        public void Clear()
        {
            _mapping.Clear();
        }

        public bool Contains(string fieldName)
        {
            return _mapping.ContainsKey(fieldName);
        }

        public Field this[string fieldName]
        {
            get
            {
                if (_mapping.ContainsKey(fieldName))
                    return _mapping[fieldName];
                else
                    throw new KeyNotFoundException();
            }
        }

        public object GetValueForField(string fieldName, IFeature sourceFeature)
        {
            Field field;
            if (_mapping.TryGetValue(fieldName, out field))
            {
                if (field.Ignore)
                    throw new InvalidOperationException(string.Format("Cannot retrieve value for field '{0}' because it is meant to be ignored.", fieldName));

                if (field.UseDefault)
                    return field.DefaultValue;

                if (field.UseSpecialLogic)
                    return _ProcessSpecial(field, sourceFeature);

                object sourceValue = DBNull.Value;
                int sourceField = sourceFeature.Fields.FindField(field.SourceField);
                if (sourceField != -1)
                {
                    sourceValue = sourceFeature.get_Value(sourceField);
                }

                if (field.UseValueMapping)
                {
                    object outValue;
                    if (field.ValueMapping.TryGetValue(sourceValue, out outValue))
                    {
                        return outValue;
                    }
                    else
                    {
                        return DBNull.Value;
                    }
                }

                if (sourceValue == DBNull.Value)
                {
                    return DBNull.Value;
                }
                else
                {
                    return Convert.ChangeType(sourceValue, field.FieldType);
                }
            }
            else
            {
                throw new IndexOutOfRangeException(string.Format("The field '{0}' is not included in the field mapping.", fieldName));
            }
        }

        public void UpdateValueForField(string fieldName, IFeature linkFeature)
        {
            Field field;
            if (_mapping.TryGetValue(fieldName, out field))
            {
                if (field.Ignore)
                    return;

                if(field.AutoUpdate)
                {
                    int fieldIndex = linkFeature.Fields.FindField(fieldName);
                    if (fieldIndex > -1)
                    {
                        if (field.UseDefault)
                            linkFeature.set_Value(fieldIndex, field.DefaultValue);

                        if (field.UseSpecialLogic)
                            _UpdateSpecial(field, linkFeature);
                    }
                }
            }
            else
            {
                throw new IndexOutOfRangeException(string.Format("The field '{0}' is not included in the field mapping.", fieldName));
            }
        }

        private object _ProcessSpecial(Field field, IFeature sourceFeature)
        {
            try
            {
                switch (field.Name)
                {
                    case "us_x_coord":
                        return ((IPolyline)sourceFeature.Shape).FromPoint.X;
                    case "us_y_coord":
                        return ((IPolyline)sourceFeature.Shape).FromPoint.Y;
                    case "ds_x_coord":
                        return ((IPolyline)sourceFeature.Shape).ToPoint.X;
                    case "ds_y_coord":
                        return ((IPolyline)sourceFeature.Shape).ToPoint.Y;
                    case "roughness":
                        string pipe_shape = this.GetValueForField("pipe_shape_deprecated", sourceFeature) as string;
                        if (pipe_shape == "CIRC")
                            return 0.013;
                        else
                            return 0.015;
                    case "edited_by":
                        return Environment.UserName;
                    case "append_date":
                    case "edit_date":
                        return DateTime.Now;
                    case "design_flow":
                        return _CalculateManningsFlow(sourceFeature);
                    case "pipe_shape":
                        return _LookupShapeCode(sourceFeature);
                    case "us_node_id":
                        return _ExtractNodeId(sourceFeature, true);
                    case "ds_node_id":
                        return _ExtractNodeId(sourceFeature, false);
                    case "link_name":
                        // This field is ignored until commit
                        return DBNull.Value;
                    default:
                        System.Diagnostics.Debug.WriteLine(string.Format("Special handling for field '{0}' not implemented, yet!", field.Name));
                        return DBNull.Value;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Failed handle special field '{0}': {1}: {2}", field.Name, ex.GetType().FullName, ex.Message));
                return DBNull.Value;
            }
        }

        private void _UpdateSpecial(Field field, IFeature linkFeature)
        {
            try
            {
                int fieldIndex = linkFeature.Fields.FindField(field.Name);
                if (fieldIndex > -1)
                {
                    switch (field.Name)
                    {
                        case "us_x_coord":
                            linkFeature.set_Value(fieldIndex, ((IPolyline)linkFeature.Shape).FromPoint.X);
                            break;
                        case "us_y_coord":
                            linkFeature.set_Value(fieldIndex, ((IPolyline)linkFeature.Shape).FromPoint.Y);
                            break;
                        case "ds_x_coord":
                            linkFeature.set_Value(fieldIndex, ((IPolyline)linkFeature.Shape).ToPoint.X);
                            break;
                        case "ds_y_coord":
                            linkFeature.set_Value(fieldIndex, ((IPolyline)linkFeature.Shape).ToPoint.Y);
                            break;
                        case "roughness":
                            string pipe_shape = linkFeature.get_Value(linkFeature.Fields.FindField("pipe_shape_deprecated")) as string;
                            if (pipe_shape == "CIRC")
                                linkFeature.set_Value(fieldIndex, 0.013);
                            else
                                linkFeature.set_Value(fieldIndex, 0.015);
                            break;
                        case "edited_by":
                            linkFeature.set_Value(fieldIndex, Environment.UserName);
                            break;
                        case "append_date":
                        case "edit_date":
                            linkFeature.set_Value(fieldIndex, DateTime.Now);
                            break;
                        case "design_flow":
                            _UpdateManningsFlow(linkFeature, fieldIndex);
                            break;
                        case "us_node_id":
                            _UpdateNodeId(linkFeature, true, fieldIndex);
                            break;
                        case "ds_node_id":
                            _UpdateNodeId(linkFeature, false, fieldIndex);
                            break;
                        default:
                            System.Diagnostics.Debug.WriteLine(string.Format("Special handling for updating field '{0}' not implemented, yet!", field.Name));
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Failed to update special field '{0}'. {1}: {2}", field.Name, ex.GetType().FullName, ex.Message));
            }
        }

        private object _ExtractNodeId(IFeature sourceFeature, bool upstream)
        {
            int dmeNodeField = sourceFeature.Fields.FindField(upstream ? "FRM_NODE" : "TO_NODE");
            if (dmeNodeField > -1)
            {
                object nodeName = sourceFeature.get_Value(dmeNodeField);

                IFeatureClass nodeClass = EmgaatsMaintenanceExtension.Extension.Configuration.MasterNodesEditClass;
                IQueryFilter filter = new QueryFilterClass();
                filter.WhereClause = string.Format("node_name = '{0}'", nodeName);

                int nodeIdField = nodeClass.FindField("node_id");
                IFeatureCursor cursor = nodeClass.Search(filter, false);
                try
                {
                    IFeature node = cursor.NextFeature();
                    if (node != null)
                    {
                        try
                        {
                            return node.get_Value(nodeIdField);
                        }
                        finally
                        {
                            EmgaatsMaintenanceExtension.ReleaseComObject(node);
                        }
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }
            }

            return DBNull.Value;
        }

        private void _UpdateNodeId(IFeature linkFeature, bool upstream, int fieldIndex)
        {
            int nodeNameField = linkFeature.Fields.FindField(upstream ? "us_node_name_deprecated" : "ds_node_name_deprecated");
            if (nodeNameField > -1)
            {
                object nodeName = linkFeature.get_Value(nodeNameField);

                IFeatureClass nodeClass = EmgaatsMaintenanceExtension.Extension.Configuration.MasterNodesEditClass;
                IQueryFilter filter = new QueryFilterClass();
                filter.WhereClause = string.Format("node_name = '{0}'", nodeName);

                int nodeIdField = nodeClass.FindField("node_id");
                IFeatureCursor cursor = nodeClass.Search(filter, false);
                try
                {
                    IFeature node = cursor.NextFeature();
                    if (node != null)
                    {
                        try
                        {
                            linkFeature.set_Value(fieldIndex, node.get_Value(nodeIdField));
                        }
                        finally
                        {
                            EmgaatsMaintenanceExtension.ReleaseComObject(node);
                        }
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }
            }
        }

        private object _LookupShapeCode(IFeature sourceFeature)
        {
            string pipe_shape = this.GetValueForField("pipe_shape_deprecated", sourceFeature) as string;

            Configuration config = EmgaatsMaintenanceExtension.Extension.Configuration;
            IFeatureWorkspace emgaatsWorkspace = ((IDataset)config.MasterLinksEditClass).Workspace as IFeatureWorkspace;
            try
            {
                ITable shapesTable = emgaatsWorkspace.OpenTable("PIPE_SHAPES");
                IQueryFilter filter = new QueryFilterClass();
                filter.WhereClause = string.Format("shape_name = '{0}'", pipe_shape);
                ICursor cursor = shapesTable.Search(filter, false);
                try
                {
                    IRow row = cursor.NextRow();
                    if (row != null)
                    {
                        try
                        {
                            int shapeIdField = shapesTable.FindField("shape_id");
                            if (shapeIdField > -1)
                            {
                                return row.get_Value(shapeIdField);
                            }
                        }
                        finally
                        {
                            EmgaatsMaintenanceExtension.ReleaseComObject(row);
                        }
                    }
                }
                finally
                {
                    EmgaatsMaintenanceExtension.ReleaseComObject(cursor);
                }
            }
            finally
            {
                EmgaatsMaintenanceExtension.ReleaseComObject(emgaatsWorkspace);
            }

            return DBNull.Value;
        }

        private object _CalculateManningsFlow(IFeature sourceFeature)
        {
            try
            {
                string pipe_shape = this.GetValueForField("pipe_shape_deprecated", sourceFeature) as string;
                if (pipe_shape == "CIRC")
                {
                    double roughness = (double)this.GetValueForField("roughness", sourceFeature);

                    double us_h = (double)this.GetValueForField("us_invert", sourceFeature);
                    double ds_h = (double)this.GetValueForField("ds_invert", sourceFeature);
                    double length = (double)this.GetValueForField("length", sourceFeature);
                    double slope = (us_h - ds_h) / length;

                    double radius = (double)this.GetValueForField("diameter_or_width_in", sourceFeature);

                    return EmgaatsMaintenanceExtension.ManningsFlowCircular(roughness, radius / 12.0, slope, true);
                }
                else
                {
                    return DBNull.Value;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Failed to calc Mannings Flow: {0}: {1}", ex.GetType().FullName, ex.Message));
                return DBNull.Value;
            }
        }

        private void _UpdateManningsFlow(IFeature linkFeature, int fieldIndex)
        {
            string pipe_shape = linkFeature.get_Value(linkFeature.Fields.FindField("pipe_shape_deprecated")) as string;
            if (pipe_shape == "CIRC")
            {
                this.UpdateValueForField("roughness", linkFeature);
                double roughness = (double)linkFeature.get_Value(linkFeature.Fields.FindField("roughness"));

                double us_h = (double)linkFeature.get_Value(linkFeature.Fields.FindField("us_invert"));
                double ds_h = (double)linkFeature.get_Value(linkFeature.Fields.FindField("ds_invert"));
                double length = (double)linkFeature.get_Value(linkFeature.Fields.FindField("length"));
                double slope = (us_h - ds_h) / length;

                double radius = (double)linkFeature.get_Value(linkFeature.Fields.FindField("diameter_or_width_in"));

                linkFeature.set_Value(fieldIndex, EmgaatsMaintenanceExtension.ManningsFlowCircular(roughness, radius / 12.0, slope, true));
            }
        }
    }
}
